package com.gitee.zycra.future.job.common.util;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;

/**
 * @author zycra
 */
public final class DateUtil {

    public static final Long ONE_DAY_MILLISECOND = 86400000L;

    public static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    public static final DateTimeFormatter DATE_MINUTE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static final DateTimeFormatter HOUR_MINUTE_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
    public static final DateTimeFormatter MINUTE_SECOND_FORMATTER = DateTimeFormatter.ofPattern("mm:ss");
    public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
    public static final DateTimeFormatter SHORT_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");
    public static final DateTimeFormatter SHORT_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
    public static final DateTimeFormatter SHORT_DATE_MINUTE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
    public static final DateTimeFormatter SHORT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    public static final DateTimeFormatter SHORT_HOUR_MINUTE_FORMATTER = DateTimeFormatter.ofPattern("HHmm");
    public static final DateTimeFormatter SHORT_MINUTE_SECOND_FORMATTER = DateTimeFormatter.ofPattern("mmss");
    public static final DateTimeFormatter SHORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("HHmmss");

    private static final long WEEK_DAYS = 7L;

    private DateUtil() {
    }

    //==============================Date To String==============================

    /**
     * LocalDateTime类型时间转字符串
     *
     * @param date      要格式的时间
     * @param formatter 格式(本类中的静态常量)
     * @return 格式化后的字符串
     */
    public static String convertLocalDateTimeToString(LocalDateTime date, DateTimeFormatter formatter) {
        if (date == null || formatter == null) {
            return null;
        }
        return date.format(formatter);
    }

    /**
     * 时间戳(毫秒)类型时间转字符串
     *
     * @param date      要格式的时间
     * @param formatter 格式(本类中的静态常量)
     * @return 格式化后的字符串
     */
    public static String convertTimestampToString(Long date, DateTimeFormatter formatter) {
        return convertLocalDateTimeToString(convertToLocalDateTime(date), formatter);
    }

    //==============================String To Date==============================

    /**
     * 字符串转LocalDateTime类型
     *
     * @param date      要格式化的字符串
     * @param formatter 格式(本类中的静态常量)
     * @return LocalDateTime类型时间
     */
    public static LocalDateTime convertStringToLocalDateTime(String date, DateTimeFormatter formatter) {
        if (date == null || formatter == null) {
            return null;
        }
        if (MONTH_FORMATTER.equals(formatter) || DATE_FORMATTER.equals(formatter)
                || SHORT_MONTH_FORMATTER.equals(formatter) || SHORT_DATE_FORMATTER.equals(formatter)) {
            return LocalDateTime.of(LocalDate.parse(date, formatter), LocalTime.MIN);
        }
        if (HOUR_MINUTE_FORMATTER.equals(formatter) || TIME_FORMATTER.equals(formatter)
                || MINUTE_SECOND_FORMATTER.equals(formatter) || SHORT_HOUR_MINUTE_FORMATTER.equals(formatter)
                || SHORT_TIME_FORMATTER.equals(formatter) || SHORT_MINUTE_SECOND_FORMATTER.equals(formatter)) {
            return LocalDateTime.of(LocalDate.MIN, LocalTime.parse(date, formatter));
        }
        return LocalDateTime.parse(date, formatter);
    }

    /**
     * 字符串转时间戳(毫秒)类型
     *
     * @param date      要格式化的字符串
     * @param formatter 格式(本类中的静态常量)
     * @return 时间戳(秒)类型时间
     */
    public static Long convertStringToTimestamp(String date, DateTimeFormatter formatter) {
        return convertToTimestamp(convertStringToLocalDateTime(date, formatter));
    }

    //==============================Timestamp To Other==============================

    /**
     * 时间戳(毫秒)类型转LocalDateTime
     *
     * @param date 时间戳(毫秒)
     * @return LocalDateTime类型时间
     */
    public static LocalDateTime convertToLocalDateTime(Long date) {
        return date == null ? null : LocalDateTime.ofInstant(Instant.ofEpochMilli(date), ZoneId.systemDefault());
    }

    //==============================LocalDateTime To Other==============================

    /**
     * LocalDateTime类型转时间戳(毫秒)
     *
     * @param date LocalDateTime类型时间
     * @return 时间戳(秒)类型时间
     */
    public static Long convertToTimestamp(LocalDateTime date) {
        return date == null ? null : date.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    //==============================Day Start==============================

    /**
     * 获取给定LocalDateTime类型时间0点0分0秒
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间0点0分0秒
     */
    public static LocalDateTime getDayStartLocalDateTime(LocalDateTime date) {
        return date == null ? null : LocalDateTime.of(LocalDate.of(date.getYear(), date.getMonth(), date.getDayOfMonth()), LocalTime.MIN);
    }

    /**
     * 获取给定时间戳(毫秒)类型时间0点0分0秒
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间0点0分0秒
     */
    public static LocalDateTime getDayStartLocalDateTime(Long date) {
        return getDayStartLocalDateTime(convertToLocalDateTime(date));
    }

    /**
     * 获取给定LocalDateTime类型时间0点0分0秒
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间0点0分0秒
     */
    public static Long getDayStartTimestamp(LocalDateTime date) {
        return convertToTimestamp(getDayStartLocalDateTime(date));
    }

    /**
     * 获取给定时间戳(毫秒)类型时间0点0分0秒
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间0点0分0秒
     */
    public static Long getDayStartTimestamp(Long date) {
        return convertToTimestamp(getDayStartLocalDateTime(date));
    }

    //==============================Next Day Start==============================

    /**
     * 获取给定LocalDateTime类型后一天0点0分0秒
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型后一天0点0分0秒
     */
    public static LocalDateTime getNextDayStartLocalDateTime(LocalDateTime date) {
        return date == null ? null : getDayStartLocalDateTime(date.plusDays(1));
    }

    /**
     * 获取给定时间戳(毫秒)类型后一天0点0分0秒
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型后一天0点0分0秒
     */
    public static LocalDateTime getNextDayStartLocalDateTime(Long date) {
        return getNextDayStartLocalDateTime(convertToLocalDateTime(date));
    }

    /**
     * 获取给定LocalDateTime类型后一天0点0分0秒
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型后一天0点0分0秒
     */
    public static Long getNextDayStartTimestamp(LocalDateTime date) {
        return convertToTimestamp(getNextDayStartLocalDateTime(date));
    }

    /**
     * 获取给定时间戳(毫秒)类型后一天0点0分0秒
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型后一天0点0分0秒
     */
    public static Long getNextDayStartTimestamp(Long date) {
        return convertToTimestamp(getNextDayStartLocalDateTime(date));
    }

    //==============================Last Day Start==============================

    /**
     * 获取给定LocalDateTime类型时间前一天0点0分0秒
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间前一天0点0分0秒
     */
    public static LocalDateTime getLastDayStartLocalDateTime(LocalDateTime date) {
        return date == null ? null : getDayStartLocalDateTime(date.minusDays(1));
    }

    /**
     * 获取给定时间戳(毫秒)类型时间前一天0点0分0秒
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间前一天0点0分0秒
     */
    public static LocalDateTime getLastDayStartLocalDateTime(Long date) {
        return getLastDayStartLocalDateTime(convertToLocalDateTime(date));
    }

    /**
     * 获取给定LocalDateTime类型时间前一天0点0分0秒
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间前一天0点0分0秒
     */
    public static Long getLastDayStartTimestamp(LocalDateTime date) {
        return convertToTimestamp(getLastDayStartLocalDateTime(date));
    }

    /**
     * 获取给定时间戳(毫秒)类型时间前一天0点0分0秒
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间前一天0点0分0秒
     */
    public static Long getLastDayStartTimestamp(Long date) {
        return convertToTimestamp(getLastDayStartLocalDateTime(date));
    }

    //==============================Days Between Two Time==============================

    /**
     * 获取两个LocalDateTime类型时间相差天数
     *
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @return 相差天数
     */
    public static int getDaysBetween(LocalDateTime startDate, LocalDateTime endDate) {
        return getDaysBetween(convertToTimestamp(startDate), convertToTimestamp(endDate));
    }

    /**
     * 获取两个时间戳(毫秒)类型时间相差天数
     *
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @return 相差天数
     */
    public static int getDaysBetween(Long startDate, Long endDate) {
        if (startDate == null || endDate == null) {
            return Integer.MAX_VALUE;
        }
        return (int) ((endDate - startDate) / ONE_DAY_MILLISECOND);
    }

    //==============================First Day Of Month==============================

    /**
     * 获取给定LocalDateTime类型时间所在月的第一天
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间所在月的第一天
     */
    public static LocalDateTime getFirstDayOfMonthLocalDateTime(LocalDateTime date) {
        return date == null ? null : LocalDateTime.of(date.toLocalDate().with(TemporalAdjusters.firstDayOfMonth()), LocalTime.MIN);
    }

    /**
     * 获取给定时间戳(毫秒)类型时间所在月的第一天
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 间戳(毫秒)类型时间所在月的第一天
     */
    public static LocalDateTime getFirstDayOfMonthLocalDateTime(Long date) {
        return getFirstDayOfMonthLocalDateTime(convertToLocalDateTime(date));
    }

    /**
     * 获取给定LocalDateTime类型时间所在月的第一天
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间所在月的第一天
     */
    public static Long getFirstDayOfMonthTimestamp(LocalDateTime date) {
        return convertToTimestamp(getFirstDayOfMonthLocalDateTime(date));
    }

    /**
     * 获取给定时间戳(毫秒)类型时间所在月的第一天
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 间戳(毫秒)类型时间所在月的第一天
     */
    public static Long getFirstDayOfMonthTimestamp(Long date) {
        return convertToTimestamp(getFirstDayOfMonthLocalDateTime(date));
    }

    //==============================Last Day Of Month==============================

    /**
     * 获取给定LocalDateTime类型时间所在月的最后一天
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间所在月的最后一天
     */
    public static LocalDateTime getLastDayOfMonthLocalDateTime(LocalDateTime date) {
        return date == null ? null : LocalDateTime.of(date.toLocalDate().with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MIN);
    }

    /**
     * 获取给定时间戳(毫秒)类型时间所在月的最后一天
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间所在月的最后一天
     */
    public static LocalDateTime getLastDayOfMonthLocalDateTime(Long date) {
        return getLastDayOfMonthLocalDateTime(convertToLocalDateTime(date));
    }

    /**
     * 获取给定LocalDateTime类型时间所在月的最后一天
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间所在月的最后一天
     */
    public static Long getLastDayOfMonthTimestamp(LocalDateTime date) {
        return convertToTimestamp(getLastDayOfMonthLocalDateTime(date));
    }

    /**
     * 获取给定时间戳(毫秒)类型时间所在月的最后一天
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间所在月的最后一天
     */
    public static Long getLastDayOfMonthTimestamp(Long date) {
        return convertToTimestamp(getLastDayOfMonthLocalDateTime(date));
    }

    //==============================First Day Of Week==============================

    /**
     * 获取给定LocalDateTime类型时间所在周的第一天
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间所在周的第一天
     */
    public static LocalDateTime getFirstDayOfWeekLocalDateTime(LocalDateTime date) {
        return date == null ? null : date.minusDays(date.getDayOfWeek().getValue() - 1L);
    }

    /**
     * 获取给定时间戳(毫秒)类型时间所在周的第一天
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间所在周的第一天
     */
    public static LocalDateTime getFirstDayOfWeekLocalDateTime(Long date) {
        return getFirstDayOfWeekLocalDateTime(convertToLocalDateTime(date));
    }

    /**
     * 获取给定LocalDateTime类型时间所在周的第一天
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间所在周的第一天
     */
    public static Long getFirstDayOfWeekTimestamp(LocalDateTime date) {
        return convertToTimestamp(getFirstDayOfWeekLocalDateTime(date));
    }

    /**
     * 获取给定时间戳(毫秒)类型时间所在周的第一天
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间所在周的第一天
     */
    public static Long getFirstDayOfWeekTimestamp(Long date) {
        return convertToTimestamp(getFirstDayOfWeekLocalDateTime(date));
    }

    //==============================Last Day Of Week==============================

    /**
     * 获取给定LocalDateTime类型时间所在周的最后一天
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间所在周的最后一天
     */
    public static LocalDateTime getLastDayOfWeekLocalDateTime(LocalDateTime date) {
        return date == null ? null : date.plusDays(WEEK_DAYS - date.getDayOfWeek().getValue());
    }

    /**
     * 获取给定时间戳(毫秒)类型时间所在周的最后一天
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间所在周的最后一天
     */
    public static LocalDateTime getLastDayOfWeekLocalDateTime(Long date) {
        return getLastDayOfWeekLocalDateTime(convertToLocalDateTime(date));
    }

    /**
     * 获取给定LocalDateTime类型时间所在周的最后一天
     *
     * @param date LocalDateTime类型时间
     * @return LocalDateTime类型时间所在周的最后一天
     */
    public static Long getLastDayOfWeekTimestamp(LocalDateTime date) {
        return convertToTimestamp(getLastDayOfWeekLocalDateTime(date));
    }

    /**
     * 获取给定时间戳(毫秒)类型时间所在周的最后一天
     *
     * @param date 时间戳(毫秒)类型时间
     * @return 时间戳(毫秒)类型时间所在周的最后一天
     */
    public static Long getLastDayOfWeekTimestamp(Long date) {
        return convertToTimestamp(getLastDayOfWeekLocalDateTime(date));
    }

    //==============================plus days==============================

    /**
     * 获取给定LocalDateTime类型时间给定天数后的时间
     *
     * @param date LocalDateTime类型时间
     * @param days 天数
     * @return LocalDateTime类型时间给定天数后的时间
     */
    public static LocalDateTime plusDays(LocalDateTime date, int days) {
        return date == null ? null : date.plusDays(days);
    }

    /**
     * 获取给定时间戳(毫秒)类型时间给定天数后的时间
     *
     * @param date 时间戳(毫秒)类型时间
     * @param days 天数
     * @return 时间戳(毫秒)类型时间给定天数后的时间
     */
    public static Long plusDays(Long date, int days) {
        return date == null ? null : date + days * ONE_DAY_MILLISECOND;
    }

    //==============================minus days==============================

    /**
     * 获取给定LocalDateTime类型时间给定天数前的时间
     *
     * @param date LocalDateTime类型时间
     * @param days 天数
     * @return LocalDateTime类型时间给定天数前的时间
     */
    public static LocalDateTime minusDays(LocalDateTime date, int days) {
        return date == null ? null : date.minusDays(days);
    }

    /**
     * 获取给定时间戳(毫秒)类型时间给定天数前的时间
     *
     * @param date 时间戳(毫秒)类型时间
     * @param days 天数
     * @return 时间戳(毫秒)类型时间给定天数前的时间
     */
    public static Long minusDays(Long date, int days) {
        return date == null ? null : date - days * ONE_DAY_MILLISECOND;
    }

    //==============================Check Intersection Common==============================

    /**
     * 公用比较方法
     *
     * @param startDate1 开始时间1
     * @param endDate1   结束时间1
     * @param startDate2 开始时间2
     * @param endDate2   结束时间2
     * @param <T>        the type of Comparable
     * @return 是否有交集
     */
    private static <T extends Comparable<T>> boolean checkIntersectionCommon(T startDate1, T endDate1, T startDate2, T endDate2) {
        if (startDate1 == null || endDate1 == null || startDate2 == null || endDate2 == null) {
            return false;
        }
        return startDate1.compareTo(endDate2) < 0 && startDate2.compareTo(endDate1) < 0;
    }

    //==============================Check Intersection==============================

    /**
     * 检查两个LocalDateTime类型时间段是否有交集
     *
     * @param startDate1 开始时间1
     * @param endDate1   结束时间1
     * @param startDate2 开始时间2
     * @param endDate2   结束时间2
     * @return 是否有交集
     */
    public static boolean checkIntersection(LocalDateTime startDate1, LocalDateTime endDate1, LocalDateTime startDate2, LocalDateTime endDate2) {
        return checkIntersectionCommon(startDate1, endDate1, startDate2, endDate2);
    }

    /**
     * 检查两个时间段戳(毫秒)类型时间是否有交集
     *
     * @param startDate1 开始时间1
     * @param endDate1   结束时间1
     * @param startDate2 开始时间2
     * @param endDate2   结束时间2
     * @return 是否有交集
     */
    public static boolean checkIntersection(Long startDate1, Long endDate1, Long startDate2, Long endDate2) {
        return checkIntersectionCommon(startDate1, endDate1, startDate2, endDate2);
    }

    //==============================Now==============================

    /**
     * 获取系统当前时间字符串类型时间
     *
     * @param formatter 格式
     * @return 系统当前时间字符串
     */
    public static String getNow(DateTimeFormatter formatter) {
        return convertLocalDateTimeToString(LocalDateTime.now(), formatter);
    }

    /**
     * 获取系统当前时间戳(毫秒)类型时间
     *
     * @return 系统当前时间戳(毫秒)
     */
    public static long getNowTimestamp() {
        return System.currentTimeMillis();
    }

    /**
     * 获取系统当前LocalDateTime类型时间
     *
     * @return 系统当前LocalDateTime类型时间
     */
    public static LocalDateTime getNowLocalDateTime() {
        return LocalDateTime.now();
    }
}
